001 /* 002 * Copyright (c) 2005 Stephen J. McConnell 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.metro.tools; 020 021 import java.beans.IntrospectionException; 022 import java.beans.Encoder; 023 import java.beans.Expression; 024 import java.beans.DefaultPersistenceDelegate; 025 import java.io.File; 026 import java.io.IOException; 027 import java.net.URI; 028 import java.net.URISyntaxException; 029 030 import net.dpml.component.Directive; 031 import net.dpml.component.ActivationPolicy; 032 033 import net.dpml.library.info.Scope; 034 import net.dpml.library.Resource; 035 036 import net.dpml.metro.data.ComponentDirective; 037 import net.dpml.metro.data.ContextDirective; 038 import net.dpml.metro.data.CategoriesDirective; 039 import net.dpml.metro.info.LifestylePolicy; 040 import net.dpml.metro.info.CollectionPolicy; 041 import net.dpml.metro.info.PartReference; 042 import net.dpml.metro.info.Type; 043 import net.dpml.metro.info.EntryDescriptor; 044 import net.dpml.metro.builder.ComponentTypeDecoder; 045 import net.dpml.metro.data.DefaultComposition; 046 047 import net.dpml.lang.Classpath; 048 import net.dpml.lang.Part; 049 import net.dpml.lang.Info; 050 051 import net.dpml.transit.monitor.LoggingAdapter; 052 053 import net.dpml.tools.tasks.PartTask; 054 055 import org.apache.tools.ant.BuildException; 056 import org.apache.tools.ant.Project; 057 import org.apache.tools.ant.AntClassLoader; 058 import org.apache.tools.ant.types.Path; 059 060 /** 061 * Task that handles the construction of a serialized container part. 062 * 063 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 064 * @version 1.1.0 065 */ 066 public class ComponentBuilderTask extends PartTask implements PartReferenceBuilder 067 { 068 private static final String NAMESPACE = "link:xsd:dpml/lang/dpml-component#1.0"; 069 070 private static final ComponentTypeDecoder COMPONENT_TYPE_DECODER = 071 new ComponentTypeDecoder(); 072 073 private URI m_uri; 074 private String m_key; 075 private boolean m_embedded = false; 076 private String m_name; 077 private String m_classname; 078 private LifestylePolicy m_lifestyle; 079 private CollectionPolicy m_collection; 080 private ActivationPolicy m_activation = ActivationPolicy.SYSTEM; 081 private CategoriesDataType m_categories; 082 private ContextDataType m_context; 083 private PartsDataType m_parts; 084 private File m_output; 085 private Type m_type; 086 private URI m_extends; 087 private boolean m_alias = false; 088 089 /** 090 * Set the part key. 091 * @param key the key 092 */ 093 public void setKey( String key ) 094 { 095 m_key = key; 096 } 097 098 /** 099 * Set the alias production flag value. 100 * @param alias true if alias production is requested 101 */ 102 public void setAlias( boolean alias ) 103 { 104 m_alias = alias; 105 } 106 107 /** 108 * Set the extends uri feature. 109 * @param uri the uri from which the component extends 110 */ 111 public void setExtends( URI uri ) 112 { 113 m_extends = uri; 114 } 115 116 /** 117 * Set the embedded component flag. 118 * @param flag true if embedded 119 */ 120 //public void setEmbedded( boolean flag ) 121 //{ 122 // m_embedded = flag; 123 //} 124 125 /** 126 * Set the component name. 127 * @param name the component name 128 */ 129 public void setName( String name ) 130 { 131 m_name = name; 132 } 133 134 /** 135 * Set the component classname. 136 * @param classname the component type classname 137 */ 138 public void setType( String classname ) 139 { 140 m_classname = classname; 141 } 142 143 /** 144 * Set the lifestyle policy vlaue. 145 * @param policy the lifestyle policy 146 */ 147 public void setLifestyle( String policy ) 148 { 149 m_lifestyle = LifestylePolicy.parse( policy ); 150 } 151 152 /** 153 * Set the gabage collection policy value. 154 * @param policy the collection policy 155 */ 156 public void setCollection( String policy ) 157 { 158 m_collection = CollectionPolicy.parse( policy ); 159 } 160 161 /** 162 * Set the activation policy value. 163 * @param policy the activation policy 164 */ 165 public void setActivation( String policy ) 166 { 167 m_activation = ActivationPolicy.parse( policy ); 168 } 169 170 /** 171 * Create a new categories data type. 172 * @return the categories datatype 173 */ 174 public CategoriesDataType createCategories() 175 { 176 if( m_categories == null ) 177 { 178 m_categories = new CategoriesDataType(); 179 return m_categories; 180 } 181 else 182 { 183 final String error = 184 "Illegal attempt to create a duplicate categories declaration."; 185 throw new BuildException( error, getLocation() ); 186 } 187 } 188 189 /** 190 * Create a new context data type. 191 * @return the context datatype 192 */ 193 public ContextDataType createContext() 194 { 195 if( null == m_context ) 196 { 197 m_context = new ContextDataType(); 198 return m_context; 199 } 200 else 201 { 202 final String error = 203 "Illegal attempt to create a duplicate context declaration."; 204 throw new BuildException( error, getLocation() ); 205 } 206 } 207 208 /** 209 * Create a new part datatype. 210 * @return a new part datatype 211 */ 212 public PartsDataType createParts() 213 { 214 if( m_parts == null ) 215 { 216 m_parts = new PartsDataType( this ); 217 return m_parts; 218 } 219 else 220 { 221 final String error = 222 "Illegal attempt to create a duplicate parts element."; 223 throw new BuildException( error, getLocation() ); 224 } 225 } 226 227 /** 228 * Build the plugin definition. 229 * @param resource the project resource definition 230 * @return the part definition 231 */ 232 protected Part build( Resource resource ) 233 { 234 try 235 { 236 Info info = getInfo( resource ); 237 Classpath classpath = getClasspath( resource ); 238 ClassLoader classloader = createClassLoader(); 239 ComponentDirective profile = buildComponentDirective( classloader ); 240 return new DefaultComposition( 241 new LoggingAdapter( "depot" ), 242 info, classpath, null, profile ); 243 } 244 catch( Throwable e ) 245 { 246 final String error = 247 "Internal error while attempting to build an external part defintion." 248 + "\nResource: " + resource; 249 throw new BuildException( error, e, getLocation() ); 250 } 251 } 252 253 /** 254 * Return the runtime classloader. 255 * @return the classloader 256 */ 257 protected ClassLoader createClassLoader() 258 { 259 Project project = getProject(); 260 Path path = getContext().getPath( Scope.RUNTIME ); 261 File classes = getContext().getTargetClassesMainDirectory(); 262 path.createPathElement().setLocation( classes ); 263 ClassLoader parentClassLoader = getClass().getClassLoader(); 264 return new AntClassLoader( parentClassLoader, project, path, true ); 265 } 266 267 //--------------------------------------------------------------------- 268 // Builder 269 //--------------------------------------------------------------------- 270 271 /** 272 * Return a uri identitifying the builder. 273 * 274 * @return the builder uri 275 */ 276 public URI getBuilderURI() 277 { 278 return PART_BUILDER_URI; 279 } 280 281 //--------------------------------------------------------------------- 282 // PartBuilder 283 //--------------------------------------------------------------------- 284 285 /** 286 * Return a urn identitifying the part handler for this builder. 287 * 288 * @return a strategy uri 289 */ 290 public URI getPartHandlerURI() 291 { 292 return PART_HANDLER_URI; 293 } 294 295 /** 296 * Build the part. 297 * @param classloader the classloader 298 * @return the part 299 * @exception IntrospectionException if an error occurs while introspecting the component class 300 * @exception IOException if an I/O error occurs 301 * @exception ClassNotFoundException if the component class cannot be found 302 */ 303 public Directive buildDirective( ClassLoader classloader ) 304 throws IntrospectionException, IOException, ClassNotFoundException 305 { 306 String classname = getClassname(); 307 Type type = loadType( classloader, classname ); 308 return buildComponentDirective( type, classloader ); 309 } 310 311 //--------------------------------------------------------------------- 312 // PartReferenceBuilder 313 //--------------------------------------------------------------------- 314 315 /** 316 * Return the part key. 317 * 318 * @return the key 319 */ 320 public String getKey() 321 { 322 if( null == m_key ) 323 { 324 final String error = 325 "Missing key attribute for nested part reference."; 326 throw new BuildException( error ); 327 } 328 else 329 { 330 return m_key; 331 } 332 } 333 334 /** 335 * Build a part reference. 336 * @param classloader the classloader 337 * @param type the component type 338 * @return the part reference 339 * @exception IntrospectionException if an error occurs while introspecting the component class 340 * @exception IOException if an I/O error occurs 341 * @exception ClassNotFoundException if the component class cannot be found 342 */ 343 public PartReference buildPartReference( ClassLoader classloader, Type type ) 344 throws IntrospectionException, IOException, ClassNotFoundException 345 { 346 String key = getKey(); 347 Directive directive = buildComponentDirective( type, classloader ); 348 return new PartReference( key, directive ); 349 } 350 351 //--------------------------------------------------------------------- 352 // impl 353 //--------------------------------------------------------------------- 354 355 private ComponentDirective buildComponentDirective( Type type, ClassLoader classloader ) 356 throws IntrospectionException, IOException, ClassNotFoundException 357 { 358 return buildComponentDirective( classloader ); 359 } 360 361 private ComponentDirective buildComponentDirective( ClassLoader classloader ) 362 throws IntrospectionException, IOException, ClassNotFoundException 363 { 364 String classname = getClassname(); 365 Type type = loadType( classloader, classname ); 366 String id = getName( type.getInfo().getName() ); 367 log( "creating [" + id + "] using [" + classname + "]" ); 368 369 LifestylePolicy lifestyle = getLifestylePolicy(); 370 CollectionPolicy collection = getCollectionPolicy( type ); 371 ActivationPolicy activation = getActivationPolicy(); 372 CategoriesDirective categories = getCategoriesDirective(); 373 ContextDirective context = getContextDirective( classloader, type ); 374 PartReference[] parts = getParts( classloader ); 375 URI base = getBaseURI(); 376 377 // 378 // return the component profile 379 // 380 381 return new ComponentDirective( 382 id, activation, collection, lifestyle, classname, categories, 383 context, parts, base ); 384 } 385 386 private URI getBaseURI() 387 { 388 return m_extends; 389 } 390 391 private Type loadType( ClassLoader classloader, String classname ) 392 { 393 if( null == classloader ) 394 { 395 throw new NullPointerException( "classloader" ); 396 } 397 if( null == classname ) 398 { 399 throw new NullPointerException( "classname" ); 400 } 401 try 402 { 403 Resource resource = getResource(); 404 Class c = classloader.loadClass( classname ); 405 return COMPONENT_TYPE_DECODER.loadType( c, resource ); 406 } 407 catch( Throwable e ) 408 { 409 final String error = 410 "Unexpected error occured while attempting to load type [" 411 + classname 412 + "]"; 413 throw new BuildException( error, e, getLocation() ); 414 } 415 } 416 417 /** 418 * Return the component name. 419 * @param typeName the component type name (used as a default) 420 * @return the name 421 */ 422 protected String getName( String typeName ) 423 { 424 if( null == m_name ) 425 { 426 if( null != m_key ) 427 { 428 return m_key; 429 } 430 else 431 { 432 return typeName; 433 } 434 } 435 else 436 { 437 return m_name; 438 } 439 } 440 441 /** 442 * Return the component classname. 443 * @return the classname 444 */ 445 protected String getClassname() 446 { 447 if( null == m_classname ) 448 { 449 return Object.class.getName(); 450 } 451 else 452 { 453 return m_classname; 454 } 455 } 456 457 /** 458 * Return the lifestyle policy declared relative to usage. 459 * If undefined then default to the lifestyle declared by the component type. 460 * Lifestyle policies that may be declared under the 'lifestyle' attribute of 461 * a component are 'transient', 'thread' or 'singleton'. If 'transient' is supplied 462 * the assigned lefestyle policy is InfoDescriptor.TRANSIENT resulting in the 463 * creation of a new instance per request. If 'thread' is declared the assigned 464 * lifestyle policy shall be InfoDescriptor.THREAD in which case a supplied 465 * instance will be reused for all requests within the same thread of execution. 466 * If the supplied policy is 'singleton' then the established instance will be 467 * shared across consumers referencing the component. 468 * 469 * @return the lifestyle policy 470 */ 471 public LifestylePolicy getLifestylePolicy() 472 { 473 return m_lifestyle; 474 } 475 476 /** 477 * Return the collection policy. 478 * @param type the component type from which the default collection 479 * policy can be resolved if needed 480 * @return the collection policy 481 */ 482 public CollectionPolicy getCollectionPolicy( Type type ) 483 { 484 if( null == m_collection ) 485 { 486 return type.getInfo().getCollectionPolicy(); 487 } 488 else 489 { 490 return m_collection; 491 } 492 } 493 494 /** 495 * Return the activation policy. 496 * @return the component activation policy 497 */ 498 public ActivationPolicy getActivationPolicy() 499 { 500 return m_activation; 501 } 502 503 /** 504 * Return the context directive. 505 * @param classloader the classloader to use 506 * @param type the component type 507 * @return the context directive 508 * @exception IntrospectionException if a class introspection error occurs 509 * @exception IOException if an I/O error occurs 510 * @exception ClassNotFoundException if a component context class cannont be found 511 */ 512 private ContextDirective getContextDirective( ClassLoader classloader, Type type ) 513 throws IntrospectionException, IOException, ClassNotFoundException 514 { 515 String name = getName( type.getInfo().getName() ); 516 String classname = type.getInfo().getClassname(); 517 ContextDirective context = createContextDirective( classloader, type ); 518 if( null == context ) 519 { 520 // return m_profile.getContextDirective(); 521 return null; 522 } 523 524 // 525 // validate that the context directives are declared 526 // and if not - throw an exception 527 // 528 529 EntryDescriptor[] entries = type.getContextDescriptor().getEntryDescriptors(); 530 for( int i=0; i<entries.length; i++ ) 531 { 532 EntryDescriptor entry = entries[i]; 533 String key = entry.getKey(); 534 535 Directive part = context.getPartDirective( key ); 536 if( entry.isRequired() && ( null == part ) ) 537 { 538 final String error = 539 "The component model [" 540 + name 541 + "] referencing the component type [" 542 + classname 543 + "] does not declare a context entry for the non-optional entry [" 544 + key 545 + "]."; 546 throw new ConstructionException( error, getLocation() ); 547 } 548 } 549 550 // 551 // we are ship-shape 552 // 553 554 return context; 555 } 556 557 private ContextDirective createContextDirective( ClassLoader classloader, Type type ) 558 throws IntrospectionException, IOException, ClassNotFoundException 559 { 560 if( null == m_context ) 561 { 562 return null; 563 } 564 else 565 { 566 return m_context.getContextDirective( classloader, type ); 567 } 568 } 569 570 private CategoriesDirective getCategoriesDirective() 571 { 572 if( null == m_categories ) 573 { 574 //return m_profile.getCategoriesDirective(); 575 return null; 576 } 577 else 578 { 579 return m_categories.getCategoriesDirective(); 580 } 581 } 582 583 private PartReference[] getParts( ClassLoader classloader ) 584 throws IntrospectionException, IOException 585 { 586 if( null != m_parts ) 587 { 588 try 589 { 590 return m_parts.getParts( classloader, null ); 591 } 592 catch( ClassNotFoundException cnfe ) 593 { 594 final String error = 595 "Unable to load a class referenced by a nested part within a component type."; 596 throw new BuildException( error, cnfe ); 597 } 598 } 599 else 600 { 601 return new PartReference[0]; 602 } 603 } 604 605 /** 606 * Utility class used to handle uri persistence. 607 */ 608 public static class URIPersistenceDelegate extends DefaultPersistenceDelegate 609 { 610 /** 611 * Return an expressio to create a uri. 612 * @param old the old value 613 * @param encoder the encoder 614 * @return the expression 615 */ 616 public Expression instantiate( Object old, Encoder encoder ) 617 { 618 URI uri = (URI) old; 619 String spec = uri.toString(); 620 Object[] args = new Object[]{spec}; 621 return new Expression( old, old.getClass(), "new", args ); 622 } 623 } 624 625 /** 626 * Constant controller uri. 627 */ 628 public static final URI PART_HANDLER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.0.1" ); 629 630 /** 631 * Constant strategy builder uri. 632 */ 633 public static final URI STRATEGY_BUILDER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.0.1" ); 634 635 /** 636 * Constant builder uri. 637 */ 638 public static final URI PART_BUILDER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-tools#1.1.0" ); 639 640 /** 641 * Utility function to create a static uri. 642 * @param spec the uri spec 643 * @return the uri 644 */ 645 protected static URI setupURI( String spec ) 646 { 647 try 648 { 649 return new URI( spec ); 650 } 651 catch( URISyntaxException ioe ) 652 { 653 return null; 654 } 655 } 656 }